/*
 * Copyright (C) 2008  Wang Hoi <fearee@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
*/
#include <QtGlobal>
#include <QtCore>
#include <stdlib.h>
#include "buffer.h"
#include "memcache.h"
#include "filecache.h"
#include "server.h"
#include "settings.h"
#include "ts.h"

#define KEY2POS(x) (x.mid(x.lastIndexOf('_')+1).toULongLong()*chunkSize)
#define POS2KEY(x) (QString("%1_%2").arg(id).arg(QString::number((x)/chunkSize), 10, '0'))
Buffer::Buffer(const QString &as, AbstractSource *s, Server *sr):QObject(qobject_cast<QObject *>(sr)), id(as), srv(sr), src(s)
{
    currentPos = 0;
    earliestTime = 0;
    latestTime = 0;
    maxMemTime = srv->setting()->maxMemBufferTime() * 1000;
    maxFileTime = srv->setting()->maxFileBufferTime() * 1000;
    memOldest = 0;
    fileOldest = 0;
    memTimers.clear();
    fileTimers.clear();
    ts = new ts_t;
    chunkSize = srv->setting()->chunkSize();
    memCache = srv->createMemCache();
    fileCache = srv->createFileCache();
    mbuffer.clear();
    parseBuffer.clear();
    connect(src, SIGNAL(dataGot(const QByteArray &)),
            SLOT(onSourceGotData(const QByteArray &)));
    connect(src, SIGNAL(reset()), SLOT(onSourceReset()));
    connect(memCache, SIGNAL(valueGot(const QString &, const QByteArray &)),
            SLOT(onMemCacheGot(const QString &, const QByteArray &)));
    connect(memCache, SIGNAL(valueSet(const QString &, const QByteArray &)),
            SLOT(onMemCacheSet(const QString &, const QByteArray &)));
    connect(memCache, SIGNAL(errorGot(const QString &)),
            SLOT(onMemCacheErr(const QString &)));
    connect(memCache, SIGNAL(valueDel(const QString &)),
            SLOT(onMemCacheDel(const QString &)));
    connect(fileCache, SIGNAL(valueGot(const QString &, const QByteArray &)),
            SLOT(onFileCacheGot(const QString &, const QByteArray &)));
    connect(fileCache, SIGNAL(valueSet(const QString &, const QByteArray &)),
            SLOT(onFileCacheSet(const QString &, const QByteArray &)));
    connect(fileCache, SIGNAL(valueDel(const QString &)),
            SLOT(onFileCacheDel(const QString &)));
    src->init();
}
Buffer::~Buffer()
{
    qDebug() << "DeConstruct Buffer "<< id;
    onSourceReset();
    delete ts;
}
void Buffer::onSourceReset()
{
    quint16 i;
    earliestTime = 0;
    latestTime = 0;
    mbuffer.clear();
    timeMap.clear();
    memOldest = 0;
    fileOldest = 0;
    memTimers.clear();
    fileTimers.clear();
    parseBuffer.clear();
    for (i = memOldest; i <= currentPos / chunkSize; i++) {
        memCache->del(POS2KEY(i*chunkSize));
    }
    for (i = fileOldest; i <= currentPos / chunkSize; i++) {
        fileCache->del(POS2KEY(i*chunkSize));
    }
    currentPos = 0;
}
void Buffer::store(const QString &key, const QByteArray &value)
{
    QString tmp;
    QTimer *timer;
    memCache->set(key, value);
    timer = new QTimer(this);
    timer->setSingleShot(true);
    connect(timer, SIGNAL(timeout()), SLOT(onMemBufferTimeOut()));
    memTimers << timer;
    timer->start(maxMemTime);

    fileCache->set(key, value);
    timer = new QTimer(this);
    timer->setSingleShot(true);
    connect(timer, SIGNAL(timeout()), SLOT(onFileBufferTimeOut()));
    fileTimers << timer;
    timer->start(maxFileTime);
}
void Buffer::parseTS(const QByteArray &data)
{
    QByteArray buf;
    quint64 sec;
    quint64 basePos;

    basePos = currentPos - parseBuffer.size();
    parseBuffer.append(data);
    ts = new ts_t;
    while (parseBuffer.size() >= 188) {
        buf = parseBuffer.left(188);
        parseBuffer.remove(0, 188);
        ts_parse(buf.data(), ts);
        if ((ts->pusi) && (ts->pes.PDflags) && (ts->pes.stream_id == 0xe0)) {
            sec = ts->pes.pts / 90 / 1000;
            if (timeMap.empty()) {
                earliestTime = sec;
                latestTime = sec;
                timeMap.insert(sec, basePos);
                // qDebug() << "parseTS" << sec << basePos;
            }
            if (!timeMap.contains(sec)) {
                timeMap.insert(sec, basePos);
                latestTime = sec;
            }
        }
        basePos += 188;
    }
}
void Buffer::onMemCacheGot(const QString &key, const QByteArray &value)
{
    qDebug("onMemCacheGot: %s", qPrintable(key));
    emit dataReady(KEY2POS(key), value);
}
void Buffer::onMemCacheSet(const QString &key, const QByteArray &value)
{
    Q_UNUSED(value)
    // qDebug() << "onMemCacheSet:" << key;
}
void Buffer::onMemCacheDel(const QString &key)
{
    // qDebug() << "onMemCacheDel:" << key;
}
void Buffer::onMemCacheErr(const QString &key)
{
    QString tmp;
    quint64 mpos;
    if (key.startsWith("Get Failed")) {
        tmp = key.mid(key.indexOf("--") + 2);
        tmp = tmp.left(tmp.indexOf("--"));
        mpos = KEY2POS(tmp);
        fileCache->get(POS2KEY(mpos));
        return;
    }
    qDebug("onMemCacheErr: %s", qPrintable(key));
}
void Buffer::onFileCacheGot(const QString &key, const QByteArray &value)
{
    qDebug() << "onFileCacheGot:" << KEY2POS(key) << value.size();
    emit dataReady(KEY2POS(key), value);
}
void Buffer::onFileCacheSet(const QString &key, const QByteArray &value)
{
    // qDebug() << "onFileCacheSet:" << key;
    emit dataReady(KEY2POS(key), value);
}
void Buffer::onFileCacheDel(const QString &key)
{
    qDebug() << "onFileCacheDel:" << key;
}
void Buffer::onFileCacheErr(const QString &key)
{
    qDebug("onFileCacheErr: %s", qPrintable(key));
    QString tmp;
    quint64 mpos;
}
void Buffer::getData(quint64 pos)
{
    quint64 mpos = pos - (pos % chunkSize);

    if ((pos >= currentPos) || (pos < timeToPos(0))) {
        return;
    }
    if (pos >= currentPos - mbuffer.size()) {
        emit dataReady(currentPos - mbuffer.size(), mbuffer);
    } else {
        // qDebug() << POS2KEY(pos);
        memCache->get(POS2KEY(pos));
    }
}
void Buffer::onSourceGotData(const QByteArray &newData)
{
    QString key;
    QByteArray nbuf;
    quint64 chunks;
    quint64 fillSize;

//     qDebug() << "onSourceGotData:" << newData.size() << " CurrentPos:" << QString::number(currentPos);
    chunks = (mbuffer.size() + newData.size()) / chunkSize;
    if (chunks > 1) {
        qFatal("buf overflow");
    }
    parseTS(newData);
    emit dataReady(currentPos, newData);
    nbuf = newData;
    if (chunks) {
        fillSize = chunkSize - mbuffer.size();
        fillSize = qMin<quint64>(nbuf.size(), fillSize);
        mbuffer += nbuf.left(fillSize);
        store(POS2KEY(-chunkSize + currentPos + fillSize), mbuffer);
        currentPos += nbuf.size();
        mbuffer = nbuf.remove(0, fillSize);
    } else {
        mbuffer += nbuf;
        currentPos += nbuf.size();
    }
}
qint64 Buffer::timeToPos(qint64 time)
{
    qint64 t;
    if (time == 0x7FFFFFFF) {
        t = latestTime;
    } else if (time >= 0) {
        t = earliestTime + time;
    } else {
        t = latestTime + time;
    }
    if ((t < earliestTime)||(t>latestTime)) {
	// qDebug() << "TimeToPos: bad time." << earliestTime << latestTime << time;
        return -1;
    }
    while ((t < latestTime) && !timeMap.contains(t)) {
        t++;
    }
#if 0
    QString tstr;
    if (time == 0x7FFFFFFF) {
        tstr = "live";
    } else {
        tstr = QString::number(time);
    }
    // qDebug() << "TimeToPos:" << earliestTime << latestTime << time << t<< timeMap.value(t);
#endif
    return timeMap.value(t);
}
void Buffer::onMemBufferTimeOut()
{
    QTimer *timer = qobject_cast<QTimer *>(sender());
    if (!memTimers.contains(timer)) {
        return;
    }
    memCache->del(POS2KEY(memOldest*chunkSize));
    memOldest++;
    memTimers.removeOne(timer);
    timer->deleteLater();
    // qDebug() << "onMemBufferTimeOut:" << POS2KEY(memOldest*chunkSize);
}
void Buffer::onFileBufferTimeOut()
{
    QTimer *timer = qobject_cast<QTimer *>(sender());
    if (!fileTimers.contains(timer)) {
        return;
    }
    QMutableMapIterator<quint64, quint64> i(timeMap);
    fileCache->del(POS2KEY(fileOldest*chunkSize));
    fileOldest++;
    emit underBuffer(fileOldest*chunkSize);
    while (i.hasNext()) {
        i.next();
        if (i.value() < (fileOldest+1)*chunkSize) {
            i.remove();
        } else {
            break;
        }
    }
    if (timeMap.empty()) {
        qFatal("FileCache problem");
    }
    i.toFront();
    earliestTime = i.peekNext().key();
    fileTimers.removeOne(timer);
    if (timer) {
	timer->deleteLater();
    }
    qDebug() << "onFileBufferTimeOut:" << POS2KEY((fileOldest - 1)*chunkSize) << "New Time:" << earliestTime << "New Pos:" << timeMap.value(earliestTime)/chunkSize;
}
#include "buffer.moc"

